package com.itheima.pinda.service.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.itheima.pinda.DTO.OrderClassifyDTO;
import com.itheima.pinda.DTO.OrderClassifyGroupDTO;
import com.itheima.pinda.DTO.OrderSearchDTO;
import com.itheima.pinda.DTO.angency.AgencyScopeDto;
import com.itheima.pinda.authority.api.AreaApi;
import com.itheima.pinda.authority.entity.common.Area;
import com.itheima.pinda.base.R;
import com.itheima.pinda.common.exception.PdException;
import com.itheima.pinda.common.utils.EntCoordSyncJob;
import com.itheima.pinda.common.utils.Result;
import com.itheima.pinda.entity.Order;
import com.itheima.pinda.entity.OrderClassifyEntity;
import com.itheima.pinda.entity.OrderClassifyOrderEntity;
import com.itheima.pinda.enums.OrderStatus;
import com.itheima.pinda.feign.OrderFeign;
import com.itheima.pinda.feign.agency.AgencyScopeFeign;
import com.itheima.pinda.future.PdCompletableFuture;
import com.itheima.pinda.service.OrderClassifyOrderService;
import com.itheima.pinda.service.OrderClassifyService;
import com.itheima.pinda.service.TaskOrderClassifyService;
import com.itheima.pinda.utils.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 处理订单分类Service
 *
 * @author llhuang10
 * @date 2021/10/25 13:46
 */
@Slf4j
@Service
public class TaskOrderClassifyServiceImpl implements TaskOrderClassifyService {

    @Resource
    private OrderFeign orderFeign;

    @Resource
    private AreaApi areaApi;

    @Resource
    private AgencyScopeFeign agencyScopeFeign;

    @Resource
    private OrderClassifyService orderClassifyService;

    @Resource
    private OrderClassifyOrderService orderClassifyOrderService;

    public static void main(String[] args) {
        // String location = "116.490237,31.767341";
        // List<AgencyScopeDto> agencyScopeList = new ArrayList<>();
        // AgencyScopeDto dto1 = new AgencyScopeDto();
        // dto1.setAgencyId("1");
        // List<List<Map>> points1 = new ArrayList<>();
        // List<Map> points1_1 = new ArrayList<>();
        // // 点1
        // Map point1 = new HashMap();
        // point1.put("lng", "116.337566");
        // point1.put("lat", "40.067944");
        //
        // // 点2
        // Map point2 = new HashMap();
        // point2.put("lng", "116.362215");
        // point2.put("lat", "40.0741");
        //
        // points1_1.add(point1);
        // points1_1.add(point2);
        // points1.add(points1_1);
        //
        // dto1.setMutiPoints(points1);
        //
        // AgencyScopeDto dto2 = new AgencyScopeDto();
        // dto2.setAgencyId("2");
        // List<List<Map>> points2 = new ArrayList<>();
        // List<Map> points2_1 = new ArrayList<>();
        // // 点1
        // Map point3 = new HashMap();
        // point3.put("lng", "116.364809");
        // point3.put("lat", "40.056375");
        //
        // // 点2
        // Map point4 = new HashMap();
        // point4.put("lng", "116.338967");
        // point4.put("lat", "40.05632");
        //
        // points2_1.add(point3);
        // points2_1.add(point4);
        // points2.add(points2_1);
        // dto2.setMutiPoints(points2);
        //
        //
        // agencyScopeList.add(dto1);
        // agencyScopeList.add(dto2);
        // Result calculate = new TaskOrderClassifyServiceImpl().calculate(agencyScopeList, location);
        // Object agencyId = calculate.get("agencyId");
        // System.out.println("agencyId = " + agencyId);

        // 用于存放当前机构下的订单
        List<OrderClassifyDTO> orderClassifyDTOS = new ArrayList<>();
        List<OrderClassifyGroupDTO> orderClassifyGroupDTOS = new ArrayList<>();

        List<Integer> startAgencyIds = IntStream.rangeClosed(1, 3).boxed().collect(Collectors.toList());
        List<Integer> currentAgencyIds = IntStream.rangeClosed(4, 7).boxed().collect(Collectors.toList());
        List<Integer> endAgencyIds = IntStream.rangeClosed(8, 10).boxed().collect(Collectors.toList());
        Random random = new Random();
        for (int i = 0; i < 50; i++) {
            Order order = new Order();
            order.setId("1000" + i);

            OrderClassifyDTO build = OrderClassifyDTO.builder()
                    .startAgencyId(startAgencyIds.get(random.nextInt(startAgencyIds.size())).toString())
                    .endAgencyId(endAgencyIds.get(random.nextInt(endAgencyIds.size())).toString())
                    .currentAgencyId(currentAgencyIds.get(random.nextInt(currentAgencyIds.size())).toString())
                    .order(order)
                    .build();

            orderClassifyDTOS.add(build);
        }

        // 2.进行订单分类
        Map<String, List<OrderClassifyDTO>> orderClassifyDTOGroup = orderClassifyDTOS.stream()
                .collect(Collectors.groupingBy(OrderClassifyDTO::groupBy));

        OrderClassifyGroupDTO.OrderClassifyGroupDTOBuilder builder = OrderClassifyGroupDTO.builder();

        // 进行对象转换, 将当前Map对象转换为 List<OrderClassifyGroupDTO>
        orderClassifyDTOGroup.forEach((key, val) -> {
            // key -> 订单类别: xxx#xxx#xxx  startAgencyId#endAgencyId#currentAgencyId
            builder.key(key);
            // 获取原始订单对象
            List<Order> orders = val.stream()
                    .map(OrderClassifyDTO::getOrder)
                    .collect(Collectors.toList());
            OrderClassifyGroupDTO orderClassifyGroupDTO = builder.orders(orders).build();
            orderClassifyGroupDTOS.add(orderClassifyGroupDTO);
        });

        System.out.println("orderClassifyGroupDTOS = " + orderClassifyGroupDTOS);
    }

    /**
     * 订单分类 (根据订单的收件人地址所属网点将订单进行分类, 网点相同的订单分为一类)
     *
     * @param agencyId 机构id (网点或者转运中心的id) --> businessId --> 库pd_auth表pd_core_org
     * @param jobId    定时任务id
     * @param logId    日志id
     * @return List<OrderClassifyDTO>
     */
    @Override
    public List<OrderClassifyGroupDTO> execute(String agencyId, String jobId, String logId) {
        // 用于存放当前机构下的订单
        List<OrderClassifyDTO> orderClassifyDTOS = new ArrayList<>();
        List<OrderClassifyGroupDTO> orderClassifyGroupDTOS = new ArrayList<>();


        // 1.获取当前机构下所有的订单数据(分为新订单和中转订单)
        orderClassifyDTOS.addAll(this.buildNewOrder(agencyId));
        orderClassifyDTOS.addAll(this.buildTransferOrder(agencyId));

        // 2.进行订单分类
        Map<String, List<OrderClassifyDTO>> orderClassifyDTOGroup = orderClassifyDTOS.stream()
                .collect(Collectors.groupingBy(OrderClassifyDTO::groupBy));

        OrderClassifyGroupDTO.OrderClassifyGroupDTOBuilder builder = OrderClassifyGroupDTO.builder();

        // 进行对象转换, 将当前Map对象转换为 List<OrderClassifyGroupDTO>
        orderClassifyDTOGroup.forEach((key, val) -> {
            // key -> 订单类别: xxx#xxx#xxx  startAgencyId#endAgencyId#currentAgencyId
            builder.key(key);
            // 获取原始订单对象
            List<Order> orders = val.stream()
                    .map(OrderClassifyDTO::getOrder)
                    .collect(Collectors.toList());
            OrderClassifyGroupDTO orderClassifyGroupDTO = builder.orders(orders).build();
            orderClassifyGroupDTOS.add(orderClassifyGroupDTO);
        });

        // 3.将分类结果保存到数据库
        this.saveRecord(orderClassifyGroupDTOS, jobId, logId);

        return orderClassifyGroupDTOS;
    }

    /**
     * 保存订单分类结果
     *
     * @param orderClassifyGroupDTOS 订单分类列表
     * @param jobId                  任务id
     * @param logId                  日志id
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveRecord(List<OrderClassifyGroupDTO> orderClassifyGroupDTOS, String jobId, String logId) {
        orderClassifyGroupDTOS.forEach(item -> {
            if (item.isNew()) {
                log.info("新订单 保存分组信息");
                OrderClassifyEntity entity = new OrderClassifyEntity();
                entity.setId(IdUtils.get());
                entity.setJobId(jobId);
                entity.setJobLogId(logId);
                entity.setClassify(item.getKey());
                // 这里有区别
                if (!item.getKey().equals("ERROR")) {
                    entity.setStartAgencyId(item.getStartAgencyId());
                    entity.setEndAgencyId(item.getEndAgencyId());
                }
                entity.setTotal(item.getOrders().size());
                entity.setCreateDate(new Date());

                List<OrderClassifyOrderEntity> orderClassifyOrders = item.getOrders()
                        .stream()
                        .map(order -> {
                            OrderClassifyOrderEntity orderEntity = new OrderClassifyOrderEntity();
                            orderEntity.setId(IdUtils.get());
                            orderEntity.setOrderClassifyId(entity.getId());
                            orderEntity.setOrderId(order.getId());
                            return orderEntity;
                        })
                        .collect(Collectors.toList());

                item.setId(entity.getId());
                orderClassifyService.save(entity);
                orderClassifyOrderService.saveBatch(orderClassifyOrders);
            } else {
                // 为什么 中转订单不需要保存 ?
                log.info("中转订单, 查询分组信息");
                List<String> orderIds = item.getOrders()
                        .stream()
                        .map(Order::getId)
                        .collect(Collectors.toList());
                log.info("当前分组的id: {}", orderIds);

                List<OrderClassifyOrderEntity> orderClassifyOrders = orderClassifyOrderService
                        .list(Wrappers.<OrderClassifyOrderEntity>lambdaQuery()
                                .in(OrderClassifyOrderEntity::getOrderId, orderIds));

                // 为什么要用set
                Set<String> orderClassifyIds = orderClassifyOrders.stream()
                        .map(OrderClassifyOrderEntity::getOrderClassifyId)
                        .collect(Collectors.toSet());
                log.info("查询订单分组id:{}", orderClassifyIds);
                if (CollectionUtils.isEmpty(orderClassifyIds)) {
                    log.error("中转订单异常: {}", orderIds);
                    return;
                }
                item.setId(orderClassifyIds.iterator().next());
            }
        });

    }

    /**
     * 获取指定机构下的新订单. 从pd-oms模块中获取订单信息
     *
     * @param agencyId 机构id
     * @return OrderClassifyDTO列表
     */
    private List<OrderClassifyDTO> buildNewOrder(String agencyId) {
        OrderSearchDTO orderSearchDTO = new OrderSearchDTO();
        // 订单状态为网点入库
        orderSearchDTO.setStatus(OrderStatus.OUTLETS_WAREHOUSE.getCode());
        orderSearchDTO.setCurrentAgencyId(agencyId);

        // 1.调用Feign接口实现RPC远程调用, 查询当前机构下的新订单
        List<Order> orderList = orderFeign.list(orderSearchDTO);
        log.info("查询到新订单个数: {}", orderList.size());

        OrderClassifyDTO.OrderClassifyDTOBuilder builder = OrderClassifyDTO.builder();
        builder.currentAgencyId(agencyId);

        return orderList.stream()
                .map(item -> builder.order(item)                    // 原始订单
                        .startAgencyId(agencyId)                    // 起始机构
                        .endAgencyId(this.getEndAgencyId(item))     // 目的地机构 (目的地网点) 需要手动出来
                        .orderType(item.getOrderType())
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * 查询中转订单
     *
     * @param agencyId 机构id
     * @return List<OrderClassifyDTO>
     */
    private List<OrderClassifyDTO> buildTransferOrder(String agencyId) {
        OrderSearchDTO orderSearchDTO = new OrderSearchDTO();
        // 订单状态为运输中 --> 中转订单
        orderSearchDTO.setStatus(OrderStatus.IN_TRANSIT.getCode());
        orderSearchDTO.setCurrentAgencyId(agencyId);

        // 1.调用Feign接口实现RPC远程调用, 查询当前机构下的新订单
        List<Order> orderList = orderFeign.list(orderSearchDTO);
        log.info("查询到新订单个数: {}", orderList.size());

        OrderClassifyDTO.OrderClassifyDTOBuilder builder = OrderClassifyDTO.builder();
        builder.currentAgencyId(agencyId);

        return orderList.stream()
                .map(item -> builder.order(item)                    // 原始订单
                        .startAgencyId(this.getStartAgencyId(item)) // 起始机构
                        .endAgencyId(this.getEndAgencyId(item))     // 目的地机构 (目的地网点) 需要手动出来
                        .orderType(item.getOrderType())
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * 获取目的地的网点Id
     *
     * @param order 订单信息
     * @return 网点Id
     */
    private String getEndAgencyId(Order order) {
        // 1.根据当前订单获取收件人地址详细信息, 包含省市区, 例如: 安徽省六安市裕安区皖西学院本部
        String address = this.receiverFullAddress(order);
        if (StringUtils.isBlank(address)) {
            throw new PdException("收件人地址不能为空!");
        }

        // 2.调用百度地图工具类, 根据地址获取对应的经纬度坐标
        String location = EntCoordSyncJob.getCoordinate(address);
        if (StringUtils.isBlank(location)) {
            throw new PdException("收件人地址不正确!");
        }
        log.info("根据地址: {} 获取对应的坐标值: {} ", address, location);

        // 3.根据经纬度坐标获取对应的区县检测通过百度地图获取的区域, 是否和下单时选择的区域一致
        // 根据经纬度获取对应饿区域信息 (从百度官方获取)
        Map map = EntCoordSyncJob.getLocationByPosition(location);
        if (CollectionUtils.isEmpty(map)) {
            throw new PdException("根据经纬度获取区域信息为空!");
        }
        // 北京市昌平区建材城西路 --> 110114
        String adcode = (String) map.get("adcode");

        // 根据 adcode (区域编码) 查询我们系统中区域信息
        R<Area> areaR = areaApi.getByCode(adcode + "000000");
        Area area = areaR.getData();
        // 判断area.data是否为空
        if (area == null) {
            throw new PdException("没有查询到区域数据!");
        }

        if (!order.getReceiverCountyId().equals(area.getId())) {
            throw new PdException("收货地址区域id和根据坐标计算出区域不一致!");
        }

        // 4.核对通过, 查询当前区县下的所有网点
        List<AgencyScopeDto> agencyScope = agencyScopeFeign.findAllAgencyScope(String.valueOf(area.getId()),
                null, null, null);

        if (CollectionUtils.isEmpty(agencyScope)) {
            throw new PdException("无法获取收件人所在网点信息!");
        }

        // 5.计算当前区域下所有网点距离收件人地址最近的网点
        Result result = this.calculate(agencyScope, location);
        return (String) result.get("agencyId");
    }

    /**
     * 根据订单获得起始机构
     *
     * @param order 订单信息
     * @return 起始机构
     */
    private String getStartAgencyId(Order order) {
        // 1.根据当前订单获取发件人地址详细信息, 包含省市区, 例如: 安徽省六安市裕安区皖西学院本部
        String address = this.senderFullAddress(order);
        if (StringUtils.isBlank(address)) {
            throw new PdException("发件人地址不能为空!");
        }

        // 2.调用百度地图工具类, 根据地址获取对应的经纬度坐标
        String location = EntCoordSyncJob.getCoordinate(address);
        if (StringUtils.isBlank(location)) {
            throw new PdException("发件人地址不正确!");
        }
        log.info("根据地址: {} 获取对应的坐标值: {} ", address, location);

        // 3.根据经纬度坐标获取对应的区县(county)检测通过百度地图获取的区域, 是否和下单时选择的区域一致
        // 根据经纬度获取对应饿区域信息 (从百度官方获取)
        Map map = EntCoordSyncJob.getLocationByPosition(location);
        if (CollectionUtils.isEmpty(map)) {
            throw new PdException("根据经纬度获取区域信息为空!");
        }
        // 北京市昌平区建材城西路 --> 110114
        String adcode = (String) map.get("adcode");

        // 根据 adcode (区域编码) 查询我们系统中区域信息
        R<Area> areaR = areaApi.getByCode(adcode + "000000");
        Area area = areaR.getData();
        // 判断area.data是否为空
        if (area == null) {
            throw new PdException("没有查询到区域数据!");
        }

        if (!order.getSenderCountyId().equals(area.getId())) {
            throw new PdException("发件地址区域id和根据坐标计算出区域不一致!");
        }

        // 4.核对通过, 查询当前区县下的所有网点
        List<AgencyScopeDto> agencyScope = agencyScopeFeign.findAllAgencyScope(String.valueOf(area.getId()),
                null, null, null);

        if (CollectionUtils.isEmpty(agencyScope)) {
            throw new PdException("无法获取发件人所在网点信息!");
        }

        // 5.计算当前区域下所有网点距离收件人地址最近的网点
        Result result = this.calculate(agencyScope, location);
        return (String) result.get("agencyId");
    }

    /**
     * 从给定网点中查找距离指定坐标最近的网点
     *
     * @param agencyScope 网点列表
     * @param location    经纬度坐标
     * @return
     */
    private Result calculate(List<AgencyScopeDto> agencyScope, String location) {
        try {
            // 遍历机构范围集合
            for (AgencyScopeDto agencyScopeDto : agencyScope) {
                List<List<Map>> mutiPoints = agencyScopeDto.getMutiPoints();
                for (List<Map> mapList : mutiPoints) {
                    String[] originArray = location.split(",");
                    // 判断某个点是否在指定区域范围内
                    boolean flag = EntCoordSyncJob.isInScope(mapList, Double.parseDouble(originArray[0]),
                            Double.parseDouble(originArray[1]));

                    if (flag) {
                        return Result.ok().put("agencyId", agencyScopeDto.getAgencyId());
                    }
                }
            }
            return Result.error(5000, "获取网点失败!");
        } catch (NumberFormatException e) {
            e.printStackTrace();
            return Result.error(5000, "获取网点失败!");
        }
    }

    /**
     * 从Map中获取坐标值
     *
     * @param map map
     * @return lng, lat   例如 32.2341,143.342012
     */
    private String getPoint(Map map) {
        String lng = map.get("lng").toString();
        String lat = map.get("lat").toString();
        return lng + "," + lat;
    }

    /**
     * 根据订单获取对应的收件人完整地址信息
     *
     * @param order 订单信息
     * @return 收件人完整地址
     */
    private String receiverFullAddress(Order order) {
        // 省Id
        Long provinceId = Long.valueOf(order.getReceiverProvinceId());
        // 市Id
        Long cityId = Long.valueOf(order.getReceiverCityId());
        // 区Id
        Long countId = Long.valueOf(order.getReceiverCountyId());
        Set<Long> areaSet = new HashSet<>();
        areaSet.add(provinceId);
        areaSet.add(cityId);
        areaSet.add(countId);

        // 调用Feign接口
        CompletableFuture<Map<Long, Area>> future = PdCompletableFuture.areaMapFuture(areaApi, null, areaSet);
        try {
            Map<Long, Area> areaMap = future.get();
            // Area是一个字典表 省市区id <--> 对应着具体地址名称
            // 根据key (id的值), 获取对应的区域Area对象
            String provinceName = areaMap.get(provinceId).getName();
            String cityName = areaMap.get(cityId).getName();
            String countName = areaMap.get(countId).getName();

            StringBuffer sb = new StringBuffer();
            sb.append(provinceName)
                    .append(cityName)
                    .append(countName)
                    .append(order.getReceiverAddress());
            // order.getReceiverAddress()可能包含了省市区的信息 或 只是包括街道的信息
            // 这个没有关系, 百度地图API会帮我们处理这个问题
            return sb.toString();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据订单获取发件人完整地址信息
     *
     * @param order 订单信息
     * @return 发件人完整地址
     */
    private String senderFullAddress(Order order) {
        // 省Id
        Long provinceId = Long.valueOf(order.getSenderProvinceId());
        // 市Id
        Long cityId = Long.valueOf(order.getSenderCityId());
        // 区Id
        Long countId = Long.valueOf(order.getSenderCountyId());
        Set<Long> areaSet = new HashSet<>();
        areaSet.add(provinceId);
        areaSet.add(cityId);
        areaSet.add(countId);

        // 调用Feign接口
        CompletableFuture<Map<Long, Area>> future = PdCompletableFuture.areaMapFuture(areaApi, null, areaSet);
        try {
            Map<Long, Area> areaMap = future.get();
            // Area是一个字典表 省市区id <--> 对应着具体地址名称
            // 根据key (id的值), 获取对应的区域Area对象
            String provinceName = areaMap.get(provinceId).getName();
            String cityName = areaMap.get(cityId).getName();
            String countName = areaMap.get(countId).getName();

            StringBuffer sb = new StringBuffer();
            sb.append(provinceName)
                    .append(cityName)
                    .append(countName)
                    .append(order.getSenderAddressId());
            // order.getReceiverAddress()可能包含了省市区的信息 或 只是包括街道的信息
            // 这个没有关系, 百度地图API会帮我们处理这个问题
            return sb.toString();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return null;
        }
    }

}
